Skip to content

🪢 fix: Paginate MCP tools/list to Load All Tools#67

Open
TomasPalsson wants to merge 1 commit into
fix-per-user-oidc-model-list-v0.8.7-rc1from
fix/mcp-tools-list-pagination-rc
Open

🪢 fix: Paginate MCP tools/list to Load All Tools#67
TomasPalsson wants to merge 1 commit into
fix-per-user-oidc-model-list-v0.8.7-rc1from
fix/mcp-tools-list-pagination-rc

Conversation

@TomasPalsson

@TomasPalsson TomasPalsson commented Jun 19, 2026

Copy link
Copy Markdown

Cherry-pick of the MCP tools/list pagination fix onto the fix-per-user-oidc-model-list-v0.8.7-rc1 image-build branch. Same commit as upstream danny-avila#13840; verified here (tsc clean, MCP suites 95/95). The red Tests: api (shard 1/3) check is a pre-existing OIDC test failure on this branch, unrelated to this change.

Summary

I fixed MCP tool discovery silently truncating to the first page when a server paginates tools/list.

Root cause

tools/list is a cursor-paginated method in the MCP spec, but LibreChat only ever read the first page in two places:

  • MCPConnection.fetchTools (packages/api/src/mcp/connection.ts) called this.client.listTools() once and returned result.tools, discarding nextCursor.
  • MCPServerInspector (packages/api/src/mcp/registry/MCPServerInspector.ts) — which builds the agent-facing tool registry (config.toolFunctions, consumed by initializeMCPsMCPManager.getAppToolFunctions) at startup and per request — called the raw connection.client.listTools() directly in both getToolFunctions and fetchServerCapabilities.

So any server returning more tools than fit in one page only ever exposed page 1. With an aggregating gateway (observed: AWS Bedrock AgentCore Gateway returning 256 tools across 6 pages) only the first ~41 tools were registered; the rest were invisible to the agent, and invoking one failed with "This tool's MCP server is temporarily unavailable" because it was never in the registry.

Approach

  • fetchTools now follows nextCursor, passing { cursor } to listTools for each page and concatenating tools in order until the server stops returning a cursor. Two defensive guards bound the loop:
    • A page cap — mcpConfig.TOOLS_LIST_MAX_PAGES (env MCP_TOOLS_LIST_MAX_PAGES, default 50, clamped to >= 1). On hitting the cap the loop stops and warns.
    • A seenCursors set — if a server repeats a cursor it already returned (an infinite-loop signature), the loop stops early and warns.
  • MCPServerInspector.getToolFunctions and fetchServerCapabilities now route through connection.fetchTools(), so the canonical startup and per-request tool registry is fully paginated.

What is deliberately not changed / out of scope

  • The no-throw contract is preserved: on a mid-pagination error the pages already fetched are returned (consistent with the page-cap partial return); a first-page error still yields [].
  • MCPServerInspector reads the tool list twice at startup (capability summary + tool registry); this double read pre-existed the PR, so collapsing it into one fetch is left as a follow-up.
  • fetchResources and fetchPrompts share the same single-page limitation; left out to keep this PR focused.

Changes

  • packages/api/src/mcp/connection.tsfetchTools follows nextCursor with page-cap and repeated-cursor guards, returning already-fetched pages on error.
  • packages/api/src/mcp/mcpConfig.ts — adds typed TOOLS_LIST_MAX_PAGES (env MCP_TOOLS_LIST_MAX_PAGES, default 50).
  • packages/api/src/mcp/registry/MCPServerInspector.tsgetToolFunctions and fetchServerCapabilities use the paginated fetchTools().
  • New unit test plus updated MCP test mocks so the suites exercise the paginated fetchTools (including five mcpConfig mocks that previously omitted the cap).

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

Run from packages/api on Node v24.16.0:

  • npx jest src/mcp/__tests__/MCPConnectionFetchTools.test.ts — 8/8 passing. Covers single page, multi-page concat with cursor passthrough, page-cap warn, repeated-cursor warn, empty intermediate page, empty-string cursor, mid-pagination failure (already-fetched pages returned), and first-page failure ([]). Builds a real MCPConnection and injects a stub client.listTools, exercising the actual loop.
  • npx jest src/mcp/registry/__tests__/MCPServerInspector.test.ts — passing; inspector now drives the paginated path.
  • npm run test:ci — full @librechat/api suite green, no regressions.
  • tsc, ESLint, Prettier, and import-sort clean on changed files.

Test Configuration

  • Node.js: v24.16.0, Workspace: packages/api

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 026757e5-813c-407b-b19e-5301b31c3bf8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-tools-list-pagination-rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

MCP `tools/list` is cursor-paginated, but LibreChat only ever read the
first page. `MCPConnection.fetchTools()` called `client.listTools()` once
and discarded `nextCursor`, and `MCPServerInspector` — which builds the
agent-facing tool registry at startup and per request — called the raw
`client.listTools()` directly. Servers that paginate (e.g. an aggregating
gateway exposing hundreds of tools) only ever exposed page one; tools on
later pages were never registered, and invoking one returned
"This tool's MCP server is temporarily unavailable."

- `MCPConnection.fetchTools()` now follows `nextCursor` across pages and
  concatenates every page's tools, bounded by a configurable page cap
  (`MCP_TOOLS_LIST_MAX_PAGES`, default 50) and a repeated-cursor guard so a
  misbehaving server cannot loop forever. Tools already fetched are returned
  if a later page fails, and the no-throw error contract is unchanged.
- `MCPServerInspector.getToolFunctions()` and `fetchServerCapabilities()`
  now route through `fetchTools()`, so the canonical startup and
  per-request tool registry is fully paginated too.
@TomasPalsson TomasPalsson force-pushed the fix/mcp-tools-list-pagination-rc branch from 35b70e2 to ae33e21 Compare June 19, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant